---
title: Header Propagation
subtitle: Configure HTTP header propagation to subgraphs
description: Configure which HTTP headers the Apollo GraphOS Router or Apollo Router Core sends to which subgraphs. Define per-subgraph header rules, along with rules that apply to all subgraphs.
---

You can configure which HTTP headers the GraphOS Router or Apollo Route Core includes in its requests to each of your subgraphs. You can define per-subgraph header rules, along with rules that apply to _all_ subgraphs.

You define header rules in your [YAML configuration file](/router/configuration/overview/#yaml-config-file), like so:

```yaml title="router.yaml"
# ...other configuration...
headers:
  all: # Header rules for all subgraphs
    request:
      - propagate:
          matching: ^upstream-header-.*
      - remove:
          named: "x-legacy-account-id"
  subgraphs:
    products: # Header rules for just the products subgraph
      request:
        - insert:
            name: "router-subgraph-name"
            value: "products"
```

## Supported header rules

The router supports the following types of header rules:

### `propagate`

Enables you to selectively pass along headers that were included in the client's request to the router.

You can specify which headers to propagate based on a matching [regex pattern](https://docs.rs/regex/latest/regex/):

```yaml
- propagate:
    matching: .*
```

<Note>

    The router _never_ propagates so-called [hop-by-hop
    headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#hop-by-hop_headers) (such as `Content-Length`)
    when propagating by pattern.

</Note>

Alternatively, you can provide a static string via the `named` option. These `named` configurations have additional flexibility, because they support the following options:

- `default`: A value to set if _no_ value was sent by the client
- `rename`: Renames the header's key to the provided value

```yaml
- propagate:
    named: "x-user-id"
    default: "abc123"
    rename: "account-id"
```

### `remove`

Enables you to selectively remove headers that were included in the client's request to the router. Like [`propagate`](#propagate), this option can match either a static string or a [regular expression](https://docs.rs/regex/latest/regex/).

```yaml
# Do not send this subgraph the "Cookie" header.
- remove:
    named: "Cookie"
- remove:
    # Remove headers that include the legacy 'x-' prefix.
    matching: ^x-.*$
```

### `insert`

Enables you to add custom headers to requests going to a specific subgraph. These headers are strings (statics, coming from request body or from context) that originate in the router, instead of originating in the client.

- Insert static header

```yaml
- insert:
    name: "sent-from-our-apollo-router"
    value: "indeed"
```

- Insert header from context

```yaml
- insert:
    name: "sent-from-our-apollo-router-context"
    from_context: "my_key_in_context"
```

- Insert header from request body

```yaml
- insert:
    name: "sent-from-our-apollo-router-request-body"
    path: ".operationName" # It's a JSON path query to fetch the operation name from request body
    default: "UNKNOWN" # If no operationName has been specified
```


#### Example JSON path queries

Let's say you have a JSON request body with the following structure:

```json
{
  "query": "{ products { id name } }",
  "extensions": {
    "metadata": [
      {
        "app_name": "random_app_name"
      }
    ]
  }
}
```

To fetch the value of the field `app_name`, the corresponding path is `.extensions.metadata[0].app_name`.

JSON path queries always begin with a period `.`

With this configuration:

```yaml
headers:
  all:
    request:
      - insert:
          name: from_app_name
          path: .extensions.metadata[0].app_name
```

You will pass a header to all your subgraphs: `"from_app_name": "random_app_name"`

## Rule ordering

Header rules are applied in the same order they're declared, and later rules can _override_ the effects of earlier rules. Consider this example:

<p style="margin-bottom: 0;">❌</p>

```yaml title="bad_configuration.yaml"
headers:
  all:
    request:
      - remove:
          named: "test"
      - propagate:
          matching: .*
```

In this example, first any header named `test` is removed from the list of headers to propagate. However, _the list of headers to propagate is currently empty!_ Next, the `propagate` rule adds _all_ headers to the propagation list, _including_ `test`.

To correctly remove a header from the propagation list, make sure to define your `remove` rule _after_ any `propagate` rules:

<p style="margin-bottom: 0;">✅</p>

```yaml title="good_configuration.yaml"
headers:
  all:
    request:
      - propagate:
          matching: .*
      - remove:
          named: "test"
```

With this ordering, first all headers are added to the propagation list, then the `test` header is removed.

## Rule fallthrough

Headers will only propagate to a target header once via the first matching rule to do so. Ensure that defaulting of headers is done in the last rule so that other rules are not ignored:

<p style="margin-bottom: 0;">❌</p>

```yaml title="bad_configuration.yaml"
headers:
  all:
    request:
      - propagate:
          named: "some-header"
          default: "some default"
      - propagate:
          named: "some-other-header"
          rename: "some-header"
```

In this example, `some-other-header` will not be propagated to `some-header` because it has already been defaulted by the previous rule.

To correctly have fallthrough of rules make sure that any defaulting is done in the last rule:

<p style="margin-bottom: 0;">✅</p>

```yaml title="good_configuration.yaml"
headers:
  all:
    request:
      - propagate:
          named: "some-header"
      - propagate:
          named: "some-other-header"
          rename: "some-header"
          default: "some default"
```

With this ordering, the `some-other-header` will be propagated to `some-header` if `some-header` is not present. If no header is present, `some-header` will be set to the default.

## Example

Here's a complete example showing all the possible configuration options in use:

```yaml title="router.yaml"
headers:
  # Header rules for all subgraphs
  all:
    request:
      # Propagate matching headers
      - propagate:
          matching: ^upstream-header-.*
      # Propagate matching headers
      - propagate:
          named: "some-header"
          default: "default-value"
          rename: "destination-header"
      # Remove the "x-legacy-account-id" header
      - remove:
          named: "x-legacy-account-id"
      # Remove matching headers
      - remove:
          matching: ^x-deprecated-.*
      # Insert the 'my-company' header
      - insert:
          name: "my-company"
          value: "acme"
  # Subgraph-specific header rules
  subgraphs:
    products:
      request:
        # Calls to the products subgraph have the "router-subgraph-name" header set to `products`.
        - insert:
            name: "router-subgraph-name"
            value: "products"
    accounts:
      request:
        # Calls to the accounts subgraph have the "router-subgraph-name" header set to `accounts`.
        - insert:
            name: "router-subgraph-name"
            value: "accounts"
```


## Response header propagation

It is not currently possible to propagate response headers from subgraphs to clients using YAML configuration alone. However, you _can_ achieve this using [Rhai scripting](/graphos/routing/customization/rhai).

This approach relies on the fact that each request has a `context` object that can store data for the duration of that request:

1. For each subgraph response, copy header values into context.
2. For the supergraph response, copy header values from the context onto the response.

Example `router.yaml` that will use the Rhai script:

```yaml title="router.yaml"
rhai:
  main: "main.rhai"
```

Example Rhai script that collects `set-cookie` headers from subgraphs and merges them into a single client response header:

```rhai title="./rhai/main.rhai"
fn supergraph_service(service) {
  let add_cookies_to_response = |response| {
    if response.context["set_cookie_headers"]?.len > 0 {
      response.headers["set-cookie"] = response.context["set_cookie_headers"];
    }
  };

  service.map_response(add_cookies_to_response);
}

fn subgraph_service(service, subgraph) {
  let store_cookies_from_subgraphs = |response| {
    if "set-cookie" in response.headers {
      if response.context["set_cookie_headers"] == () {
        response.context.set_cookie_headers = []
      }

      response.context.set_cookie_headers += response.headers.values("set-cookie");
    }
  };

  service.map_response(store_cookies_from_subgraphs);
}
```

<Note>

    If you require a configuration-based solution for response header propagation, [please leave a comment on our issue
    tracker](https://github.com/apollographql/router/issues/1284).

</Note>

## Propagation between subgraphs

It is not currently possible to propagate headers between subgraphs using YAML config alone. However, you _can_ achieve this using [Rhai scripting](/graphos/routing/customization/rhai).

This approach relies on the fact that each request has a `context` object that can store data for the duration of that request:

1. For each subgraph response, copy header values into context.
2. For each subgraph request, copy header values from context into the subgraph request.

Example `router.yaml` that will use the Rhai script:

```yaml title="router.yaml"
rhai:
  main: "main.rhai"
```

Example Rhai script that copies `request-id` and `user` headers:

```rhai title="./rhai/main.rhai"
fn subgraph_service(service, subgraph) {
    // The list of headers that you which to propagate.
    let headers = ["request-id", "user"];

    // Callback for subgraph requests. Inserts headers from context into the subgraph request.
    let request_callback = |request| {
        for key in headers {
            if request.context[key] != () {
                request.subgraph.headers[key] = request.context[key];
            }
        }
    };

    // Callback for subgraph responses. Pulls header values out of the response and inserts them into context.
    let response_callback = |response| {
        for key in headers {
            if key in response.headers {
                response.context[key] = response.headers[key];
            }
        }
    };

    // Register the callbacks.
    service.map_request(request_callback);
    service.map_response(response_callback);
}
```
